Skip to content

Conversation

@zombieJ
Copy link
Member

@zombieJ zombieJ commented Nov 26, 2025

ref ant-design/ant-design#55928

Summary by CodeRabbit

  • Bug Fixes(缺陷修复)

    • 改进了下拉/输入框的失焦处理,减少意外关闭场景并确保 onBlur 仍被触发
  • Refactor(重构)

    • 简化并统一了打开/关闭控制流程,部分关闭操作改为延迟执行以改善交互稳定性
  • Tests(测试)

    • 增强了与失焦相关的测试,使对焦/失焦行为的断言更确定性、更可靠

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
select Ready Ready Preview Comment Nov 27, 2025 3:02am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

移除 BaseSelect 的模糊时立即懒关闭调用;在 SelectInput 的 blur 处理中使用由 useOpen 导出的 macroTask 延后执行关闭判断并在必要时调用开关;useOpen 导出 macroTask 并收窄 TriggerOpenType 配置,测试中增加显式的 blur() 调用以稳定位移行为。

Changes

结群 / 文件 变更摘要
BaseSelect 模糊处理
src/BaseSelect/index.tsx
onInternalBlur 中移除 triggerOpen(false, { lazy: true }) 调用(不再在 blur 时显式触发懒关闭)
SelectInput 延迟关闭与引用扩展
src/SelectInput/index.tsx
导入并使用 macroTask 延迟执行 blur 后的 toggleOpen(false) 检查;仅当输入非活跃或不包含活跃元素时才关闭;扩展 SelectInputRef 为包含 blur()nativeElement
useOpen 钩子与工具导出
src/hooks/useOpen.ts
macroTask 导出以供外部使用;TriggerOpenType 配置移除 lazy 字段(仅保留 ignoreNext);简化 toggleOpen 条件逻辑
测试:显式触发 blur
tests/Combobox.test.tsx, tests/Select.test.tsx
在若干用例中增加对输入元素的显式 blur() 调用(配合计时器/act),使模糊相关断言更确定性

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Input as SelectInput
    participant MacroTask as macroTask (useOpen)
    participant OpenHook as toggleOpen / useOpen

    User->>Input: 触发 blur 事件
    activate Input
    Input->>MacroTask: 注册宏任务延迟执行检查
    deactivate Input

    Note over MacroTask: 等待当前微/宏任务完成(延后执行)

    MacroTask->>Input: 运行延迟回调 -> 检查 document.activeElement
    alt 输入仍为活跃或包含活跃元素
        Note right of Input: 跳过关闭
    else 输入非活跃
        Input->>OpenHook: 调用 toggleOpen(false)
        OpenHook->>OpenHook: 更新 open 状态
    end
Loading

Estimated code review effort

🎯 3 (中等) | ⏱️ ~20 分钟

需要额外关注的文件/点:

  • src/hooks/useOpen.ts — 导出 macroTaskTriggerOpenType 公共签名变化可能影响调用方;
  • src/SelectInput/index.tsx — 延迟关闭的时序与 document.activeElement 检查;新增 SelectInputRef 字段兼容性;
  • 测试改动 — 新增的显式 blur() 是否覆盖所有相关场景(自定义输入、组合框等)。

Possibly related PRs

Suggested reviewers

  • afc163

Poem

🐰 微风轻拂框中光,

模糊来时不匆忙,
宏任务悄候把关房,
若非真离开便留芳,
选项安稳又舒畅 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR标题准确总结了主要变更:修复了当焦点仍在输入框时,blur事件不应触发dropdown关闭的问题。
Linked Issues check ✅ Passed 代码变更直接解决了linked issue #55928中的问题:修复了AutoComplete自定义输入组件中input获取焦点后下拉框自动隐藏的bug。
Out of Scope Changes check ✅ Passed 所有代码变更均与解决blur事件触发的不必要关闭行为相关,包括钩子函数、组件公共API的扩展和测试用例的完善,无超出范围的改动。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch blur-close

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @zombieJ, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug in Ant Design's select components where the dropdown would close unexpectedly when the input field lost focus, even if the focus was still within a related element of the component. The fix introduces a more robust blur handling mechanism that defers the decision to close the dropdown until it's confirmed that the focus has truly left the component, thereby improving the user experience and preventing premature closures.

Highlights

  • Refined Blur Handling in Select Components: The core change prevents the select dropdown from closing prematurely when the input field loses focus but the document.activeElement remains within the component's input reference. This is achieved by introducing a macroTask to check the active element after a blur event.
  • Conditional Dropdown Closure: The onInternalBlur handler in SelectInput now conditionally calls toggleOpen(false). It only closes the dropdown if the focus has truly moved outside the input element or its contained elements, preventing unintended closures.
  • Simplified BaseSelect Blur Logic: The BaseSelect component no longer directly calls triggerOpen(false) on blur, delegating this responsibility to the more granular SelectInput component's refined blur logic.
  • Exported macroTask Utility: A macroTask utility function has been exported from useOpen.ts to allow for deferred execution, which is crucial for checking the document.activeElement reliably after a blur event.
  • Updated Test Cases: Existing test cases for Combobox and Select components have been updated to explicitly trigger blur() on the input element, ensuring comprehensive testing of the new blur handling logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.41%. Comparing base (6b3a9fa) to head (14b6ae3).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1175   +/-   ##
=======================================
  Coverage   99.41%   99.41%           
=======================================
  Files          31       31           
  Lines        1196     1200    +4     
  Branches      425      428    +3     
=======================================
+ Hits         1189     1193    +4     
  Misses          7        7           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively resolves an issue where the select dropdown would close prematurely on blur. The solution, which involves deferring the close logic using a macroTask to check the document.activeElement after focus has shifted, is a solid approach. The related refactoring in useOpen to always handle closing asynchronously simplifies the logic. The test updates are also good, as they more accurately simulate real browser behavior for blur events. I have one minor suggestion to remove some commented-out code to keep the codebase clean.

Comment on lines 573 to 575
// triggerOpen(false, {
// lazy: true,
// });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This commented-out code is no longer necessary since the logic for closing the dropdown on blur has been moved to the SelectInput component. It should be removed to improve code clarity.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/BaseSelect/index.tsx (1)

557-578: 建议移除注释代码或添加说明。

triggerOpen(false, { lazy: true }) 调用被注释掉,因为 blur 关闭逻辑已移至 SelectInput 中处理。这是正确的架构调整,但代码库中留下注释代码会降低可维护性。

建议:

  1. 直接移除注释代码(首选),或
  2. 添加详细的 TODO 注释说明为何保留,例如:
  const onInternalBlur: React.FocusEventHandler<HTMLElement> = (event) => {
    setFocused(false);

    if (mergedSearchValue) {
      // `tags` mode should move `searchValue` into values
      if (mode === 'tags') {
        onSearch(mergedSearchValue, { source: 'submit' });
      } else if (mode === 'multiple') {
        // `multiple` mode only clean the search value but not trigger event
        onSearch('', {
          source: 'blur',
        });
      }
    }

    if (!disabled) {
-     // triggerOpen(false, {
-     //   lazy: true,
-     // });
+     // Note: Blur close logic moved to SelectInput to check activeElement before closing
+     // This prevents closing when focus moves to another input within the component
      onBlur?.(event);
    }
  };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b3a9fa and dd860f6.

📒 Files selected for processing (5)
  • src/BaseSelect/index.tsx (1 hunks)
  • src/SelectInput/index.tsx (2 hunks)
  • src/hooks/useOpen.ts (2 hunks)
  • tests/Combobox.test.tsx (2 hunks)
  • tests/Select.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/SelectInput/index.tsx (2)
src/hooks/useOpen.ts (1)
  • macroTask (10-19)
tests/utils/common.ts (1)
  • toggleOpen (22-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
src/hooks/useOpen.ts (4)

10-19: 导出 macroTask 工具函数是合理的。

macroTask 导出使得其他模块(如 SelectInput)可以使用延迟执行机制来处理异步逻辑,这与 PR 修复 blur 关闭逻辑的目标一致。


70-76: 解构更新与类型定义保持一致。

移除 lazy 参数的解构与 TriggerOpenType 的类型更新一致。


79-93: 简化的 toggleOpen 逻辑符合新的 blur 处理机制。

移除 lazy 条件后,关闭操作始终通过 macroTask 延迟执行,这允许在 SelectInput 的 blur 处理中正确检查 document.activeElement,确保焦点仍在输入框内时不会关闭下拉框。


21-31: The review comment is incorrect—lazy was not removed from the type definition.

The lazy property still exists in TriggerOpenType at line 29 of src/hooks/useOpen.ts. While it was never actually implemented (the only reference is commented-out code in src/BaseSelect/index.tsx line 574), it remains in the type signature. This is dead code that should be cleaned up, not a breaking change being introduced. Since lazy was never functional, no external consumers could be depending on it.

Likely an incorrect or invalid review comment.

src/SelectInput/index.tsx (1)

14-14: 导入 macroTask 用于延迟 blur 处理。

useOpen 导入 macroTask 工具函数,用于实现下方的延迟关闭逻辑。

tests/Select.test.tsx (1)

808-815: 测试更新正确模拟了新的 blur 行为。

显式调用 input.blur() 并在 act 块中运行所有定时器,确保 blur 处理逻辑(包括 macroTask 延迟的关闭检查)在断言之前完成。这使测试更具确定性,并正确验证了 blur 不会在焦点保持时关闭下拉框的修复。

tests/Combobox.test.tsx (2)

505-510: 测试正确模拟了 blur 事件处理。

显式调用 input.blur() 确保浏览器的 activeElement 状态更新,随后的 delay(100) 等待 macroTask 延迟的关闭逻辑执行完成。


594-599: 测试更新与其他测试文件保持一致。

act 块中调用 input.blur()jest.runAllTimers() 的模式与 Select.test.tsx 中的更新一致,确保 blur 处理逻辑完整执行后再进行断言。

Comment on lines 200 to 209
const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
toggleOpen(false);
macroTask(() => {
const inputNode = getDOM(inputRef.current);
if (inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
toggleOpen(false);
}
});

onBlur?.(event);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

blur 处理逻辑修复了焦点转移问题,但需要添加空值检查。

使用 macroTask 延迟关闭逻辑确保在 document.activeElement 更新后再检查焦点位置,这正确修复了当焦点转移到同一组件内的其他输入元素时错误关闭下拉框的问题。

但是,getDOM(inputRef.current) 可能返回 null,应添加空值检查以避免潜在的运行时错误。

应用以下 diff 添加空值检查:

  const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
    macroTask(() => {
      const inputNode = getDOM(inputRef.current);
-     if (inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
+     if (inputNode && inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
        toggleOpen(false);
      }
    });

    onBlur?.(event);
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
toggleOpen(false);
macroTask(() => {
const inputNode = getDOM(inputRef.current);
if (inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
toggleOpen(false);
}
});
onBlur?.(event);
};
const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
macroTask(() => {
const inputNode = getDOM(inputRef.current);
if (inputNode && inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
toggleOpen(false);
}
});
onBlur?.(event);
};
🤖 Prompt for AI Agents
In src/SelectInput/index.tsx around lines 200 to 209, the blur handler uses
getDOM(inputRef.current) without checking for null which can cause a runtime
error; update the macroTask callback to first check that inputNode is not null
(or undefined) before comparing it to document.activeElement or calling
inputNode.contains(...), and only call toggleOpen(false) when inputNode exists
and the activeElement is outside it; keep the existing onBlur?.(event) call
unchanged.

@zombieJ zombieJ merged commit 3780838 into master Nov 27, 2025
12 checks passed
@zombieJ zombieJ deleted the blur-close branch November 27, 2025 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants